﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Reflection;
using AutoClub.Core.Dao.Support;
using AutoClub.Core.Domain.Dao;

namespace AutoClub.Core.Dao
{
    /// <summary>
    /// Base abstract repo for all Entity framework Repository implementation
    /// </summary>
    /// <typeparam name="TId">type of the Id key</typeparam>
    /// <typeparam name="TEntity">type of the entity to be persisted</typeparam>
    public abstract class BaseRepo<TId, TEntity>
        where TEntity : BaseEntity<TId>
        where TId : struct
    {
        #region Constructor

        /// <summary>
        /// Creates a base repo instance
        /// </summary>
        public BaseRepo(IUnitOfWork uof, DbSet<TEntity> dataSet, IIdentifierGenerator<TId> identifierGenerator)
        {
            //TO DO : Implement Unit Of Work and get the context instance out of here.
            this.DataSet = dataSet;
            this.Context = (uof as AutoClubUnitOfWork).Context;
            this.IdentifierGenerator = identifierGenerator;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Dataset for Entity framework operations
        /// </summary>
        protected DbSet<TEntity> DataSet { get; private set; }

        protected DbContext Context { get; private set; }

        protected IIdentifierGenerator<TId> IdentifierGenerator { get; private set; }

        #endregion

        #region Methods

        /// <summary>
        /// Gets a single instance by its identifier
        /// </summary>
        /// <param name="id">Entity's di</param>
        /// <returns>The instance for this id</returns>
        public virtual TEntity Get(TId id)
        {
            return this.GetBy(e => e.Id.Equals(id) && e.Active)
                .SingleOrDefault();
        }

        /// <summary>
        /// Gets all the entities
        /// </summary>
        /// <returns>A list of entities</returns>
        public virtual IEnumerable<TEntity> GetAll()
        {
            return this.DataSet.ToList();
        }

        /// <summary>
        /// Get all the entities that satisfied a given predicate
        /// </summary>
        /// <param name="predicate">a predicate for filter</param>
        /// <returns></returns>
        public virtual IEnumerable<TEntity> GetBy(Func<TEntity, bool> predicate)
        {
            return this.DataSet.Where(predicate).ToList();
        }


        /// <summary>
        /// Persists an entity in the database
        /// </summary>
        /// <param name="entity">Entity to be persisted</param>
        /// <returns>The persisted entity</returns>
        public virtual TEntity Add(TEntity entity)
        {
            entity.Id = this.IdentifierGenerator.GenerateNewIdentifier();
            entity.Active = true;
            entity.CreatedOn = DateTime.Today;
            entity.CreatedBy = "admin"; //TO DO : change this when the current user can be retrieve from the context.

            foreach (var property in typeof(TEntity).GetProperties())
            {
                if (IsDomainClass(property))
                {
                    dynamic propertyValue = property.GetValue(entity, null);
                    propertyValue.CreatedOn = propertyValue.CreatedOn.Equals(DateTimeOffset.MinValue) ? entity.CreatedOn : propertyValue.CreatedOn;
                    propertyValue.CreatedBy = string.IsNullOrEmpty(propertyValue.CreatedBy) ? entity.CreatedBy : propertyValue.CreatedBy;
                }
            }

            return this.DataSet.Add(entity);
        }

        /// <summary>
        /// Permite saber si la property a evaluar es una propiedad de tipo del dominio de la app
        /// </summary>
        /// <param name="property"></param>
        /// <returns></returns>
        private bool IsDomainClass(PropertyInfo property)
        {
            return property.PropertyType.IsClass &&
                property.PropertyType.Namespace.StartsWith(typeof(TEntity).Namespace);
        }

        /// <summary>
        /// Updates the current entity
        /// </summary>
        /// <param name="entity">entity to update</param>
        /// <returns></returns>
        public virtual TEntity Update(TEntity entity)
        {
            this.Context.Entry(entity).State = EntityState.Modified;
            return entity;
        }

        /// <summary>
        /// Removes the current entity of the system.
        /// </summary>
        /// <param name="entity">Entity to be removed</param>
        /// <returns></returns>
        public virtual TEntity Remove(TEntity entity)
        {
            entity.Active = false;
            return entity;
        }

        #endregion

    }
}
